package base.thread_demo.demo15;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 利用 ReentrantReadWriteLock 实现一个缓存系统,该案例只是一个雏形.
 * 缓存系统:很多的线程来根据id获取User的实例,每次查询数据库是非常耗时的,为此,缓存系统只会让一个线程去查询,
 * 然后将查询到的数据保存在缓存系统内存中.本案例模拟的就是这么个操作
 *
 *  class CachedData {
 *    Object data;
 *    volatile boolean cacheValid;
 *    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 *
 *    void processCachedData() {
 *      rwl.readLock().lock();
 *      if (!cacheValid) {
 *         // Must release read lock before acquiring write lock
 *         rwl.readLock().unlock();// 假如这里有三个线程同时到达这里，必须等这三个线程都释放掉了读锁，才能挂上写锁。
 *         rwl.writeLock().lock();
 *         // Recheck state because another thread might have acquired
 *         //   write lock and changed state before we did.
 *         if (!cacheValid) {
 *           data = ...
 *           cacheValid = true;
 *         }
 *         // Downgrade by acquiring read lock before releasing write lock
 *         rwl.readLock().lock(); //当前线程在writeLock().unlock()释放之前先readLock().lock()，将写锁降级，挂上读锁，此时其他线程依旧
 *         // 因当前线程写锁没有释放，而挂不上读锁，无法读取。这里先降级写锁挂上读锁，好处在于：假如上面三个线程同时处于readLock().unlock()
 *         // ，此时cpu时间片切换去writeLock().unlock()了,cpu时间片转而又切换到上面三个线程之一，此时又writeLock().lock()上了写锁，不好。
 *         // 这里先降级写锁挂上读锁，然后释放写锁，就不会出现时间片切换到上面三个线程再次进入写锁而不能读问题，当我先挂读锁，再释放写锁，
 *         // 一旦写锁释放，只剩读锁，其他线程（除那三个线程）就可以读数据了。至于前面那三个已经进入if内部的，还是需要挂写锁的。
 *         rwl.writeLock().unlock(); // Unlock write, still hold read
 *      }
 *
 *      use(data);
 *      rwl.readLock().unlock();
 *    }
 *  }
 */
public class CacheSystem {
    private Map<String, Object> map = new HashMap<>();
    public static void main(String[] args) {
        CacheSystem cs = new CacheSystem();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                cs.getUserById("110");//假设都来获取id=110的数据库数据
            },"t"+i).start();
        }
    }

    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    public Object getUserById(String id){
        rwl.readLock().lock();// 一开始尝试读取数据,所以上的读锁
        Object user = null;
        try {
            user = map.get(id);
            if(user == null){//如果从缓存中获取的数据为null,那么开始解锁读锁,开始准备上写锁
                rwl.readLock().unlock();// 假设同时有t1,t2,t3同时到达这里上了读锁
                rwl.writeLock().lock();//t1,t2,t3随机其中某个给上了写锁,此时另外两个读锁线程和写锁线程(如果有上写锁的话)都将会被排斥在外
                try {
                    // 假设t1已经查询完数据并解锁了写锁,t2,t3其中还的按照上写锁,查询库,解锁写锁逻辑走完流程,故这里害的if判断一次
                    if(user == null) {
                        user = "模拟查询数据库数据";//去数据库查询数据
                    }
                }finally {
                    rwl.readLock().lock();// 一个线程的内部拿到写锁,那么还是可以拿到读锁的,这就是锁的降级,成为读写锁
                    rwl.writeLock().unlock();
                }
            }
        }finally {
            rwl.readLock().unlock();
        }


        return user;
    }

}
